/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "sv_local.h"

/*
 ==================
 SV_DropClient

 Called when the client is totally leaving the server, either willingly or
 unwillingly. This is NOT called if the entire server is quiting or crashing.
 ==================
*/
void SV_DropClient (client_t *client, const char *reason){
	if (client->state <= CS_ZOMBIE)
		return;		// Already dropped


	// Disconnect the client from the game
	sg->ClientDisconnect(client->clientNum);

	// Update number of clients for server info
	CVar_SetInteger(sv_numClients, sv_numClients->integerValue - 1);

	// Drop the client, it will become free in a few seconds
	Com_DPrintf("Going to CS_ZOMBIE for %s\n", client->name);

	client->state = CS_ZOMBIE;

	// Tell everyone why this client got dropped
	SV_BroadcastReliableCommand("print %s" S_COLOR_DEFAULT " disconnected - %s\n", client->name, reason);

}

/*
 ==================
 SV_UserInfoChanged

 The user info string for a client has been changed, so parse new information
 out of it. This will happen when the client connects to the server, and
 possibly during gameplay.
 ==================
*/
void SV_UserInfoChanged (client_t *client){

	const char	*infoString;
	char		*ch;
	int			value;

	// Parse all the information we need
	infoString = client->userInfo;

	while (1){
		ch = Str_FindChar(infoString, '\\');
		if (!ch)
			break;

		*ch++ = 0;

		// Check the name/value pair
		Cmd_TokenizeString(infoString);

		if (!Str_ICompare(Cmd_Argv(0), "cl_playerName"))
			Str_Copy(client->name, Cmd_Args(1, -1, false), sizeof(client->name));

		// Advance
		infoString = (const char *)ch;

		*--ch = '\\';
	}
}


/*
 ==============================================================================

 CLIENT COMMAND EXECUTION

 ==============================================================================
*/


/*
 ==================
 SV_UserInfo_f
 ==================
*/
static void SV_UserInfo_f (client_t *client){

	Str_Copy(client->userInfo, Cmd_Argv(1), sizeof(client->userInfo));

	// Give the game a chance to modify the user info
	sg->ClientUserInfoChanged(client->clientNum, client->userInfo);

	// Parse new information
	SV_UserInfoChanged(client);
}

/*
 ==================
 SV_Disconnect_f
 ==================
*/
static void SV_Disconnect_f (client_t *client){

	SV_DropClient(client, "Disconnected from server");
}



// ============================================================================

typedef struct {
	const char *	name;
	void			(*function)(client_t *);
} clientCmd_t;

static clientCmd_t	sv_clientCmds[] = {
	{"userInfo",		SV_UserInfo_f},
	{"disconnect",		SV_Disconnect_f},
	{NULL,				NULL}
};


/*
 ==================
 SV_ExecuteClientCommand
 ==================
*/
static bool SV_ExecuteClientCommand (client_t *client, const char *command, bool allowGameCommand){

	clientCmd_t	*clientCmd;
	const char	*name;

	Com_DPrintf("Client command from %s" S_COLOR_DEFAULT ": %s\n", client->name, command);

	// Execute the client command
	Cmd_TokenizeString(command);

	if (!Cmd_Argc())
		return false;	// No tokens

	name = Cmd_Argv(0);

	// Check for a server level command
	for (clientCmd = sv_clientCmds; clientCmd->name; clientCmd++){
		if (!Str_ICompare(clientCmd->name, name)){
			clientCmd->function(client);
			return true;
		}
	}

	// Pass unknown client commands to the game module if allowed
	if (allowGameCommand){
		sg->ClientCommand(client->clientNum);
		return true;
	}

	Com_DPrintf("Client command from %s" S_COLOR_DEFAULT " ignored: %s\n", client->name, name);

	return false;
}


/*
 ==============================================================================

 CLIENT MESSAGE PARSING

 ==============================================================================
*/


/*
 ==================
 SV_ParseMove
 ==================
*/
static void SV_ParseMove (client_t *client, ByteBuffer *msg, bool delta){

	userCmd_t	nullUserCmd, userCmds[USERCMD_BACKUP];
	userCmd_t	*oldUserCmd, *newUserCmd;
	int			index, count;
	int			i;

	// Parse the user command count
	count = msg->ReadByte();

	if (count < 1)
		return;

	if (count > USERCMD_BACKUP)
		count = USERCMD_BACKUP;

	// Parse all the user commands
	oldUserCmd = &nullUserCmd;
	memset(oldUserCmd, 0, sizeof(userCmd_t));

	for (i = 0; i < count; i++){
		newUserCmd = &userCmds[i];

		newUserCmd->time = msg->ReadDeltaLong(oldUserCmd->time);
		newUserCmd->flags = msg->ReadDeltaByte(oldUserCmd->flags);
		newUserCmd->buttons = msg->ReadDeltaLong(oldUserCmd->buttons);
		newUserCmd->forwardMove = msg->ReadDeltaChar(oldUserCmd->forwardMove);
		newUserCmd->rightMove = msg->ReadDeltaChar(oldUserCmd->rightMove);
		newUserCmd->upMove = msg->ReadDeltaChar(oldUserCmd->upMove);
		newUserCmd->angles[0] = msg->ReadDeltaShort(oldUserCmd->angles[0]);
		newUserCmd->angles[1] = msg->ReadDeltaShort(oldUserCmd->angles[1]);
		newUserCmd->angles[2] = msg->ReadDeltaShort(oldUserCmd->angles[2]);

		oldUserCmd = newUserCmd;
	}

	// Ignore it if we are getting remaining parasite user commands
	if (client->serverId != sv.serverId){
		Com_DPrintf(S_COLOR_YELLOW "Ignoring outdated user command from %s\n", client->name);
		return;
	}

	// If this is the first user command we have received this server state,
	// put the client into the game
	if (client->state == CS_PRIMED){
		Com_DPrintf("Going to CS_ACTIVE for %s\n", client->name);
		client->state = CS_ACTIVE;
		client->lastUserCmd = userCmds[0];
		// Call the game begin function
		sg->ClientBegin(client->clientNum);
	}

	// Usually, the first couple user commands will be duplicates of ones we
	// have previously received, but the times in the user commands will cause
	// them to be immediately discarded
	for (i = 0; i < count; i++){
		// If this is a user command from before a map restart, ignore it
		if (userCmds[i].time > userCmds[count - 1].time)
			continue;

		// Don't execute if this is an old user command which is already
		// executed
		if (userCmds[i].time <= client->lastUserCmd.time)
			continue;

		client->lastUserCmd = userCmds[i];

		// Execute it
		sg->ClientThink(client->clientNum, &client->lastUserCmd);

		if (client->state != CS_ACTIVE)
			return;		// May have been dropped during the last user command
	}
}

/*
 ==================
 SV_ParseCommand
 ==================
*/
static bool SV_ParseCommand (client_t *client, ByteBuffer *msg){

	char	command[MAX_STRING_LENGTH];
	int		sequence;
	bool	allowGameCommand;

	sequence = msg->ReadLong();
	msg->ReadString(command, sizeof(command));

	// Malicious users may try using too many string commands to lag other
	// players. If we decide that we want to stall the command, we will stop
	// processing the rest of the packet, including the user commands. This
	// causes flooders to lag themselves but not other people.
	

	// Execute it
	return SV_ExecuteClientCommand(client, command, true);
}

/*
 ==================
 SV_ParseClientMessage
 ==================
*/
void SV_ParseClientMessage (client_t *client, ByteBuffer *msg){

	bool	allowCommands = true;
	int		op;

	// Get the server id
	client->serverId = msg->ReadLong();

	// Parse the message
	while (1){
		if (msg->getBitsRead() > msg->getBitsWritten()){
			Com_DPrintf(S_COLOR_YELLOW "Read past end of client message from %s\n", client->name);
			return;
		}

		// Parse the client op
		op = msg->ReadByte();

		if (op == CLC_EOF)
			return;

		switch (op){
		case CLC_NOP:

			break;
		case CLC_MOVE:
			SV_ParseMove(client, msg, true);
			break;
		case CLC_MOVENODELTA:
			SV_ParseMove(client, msg, false);
			break;
		case CLC_COMMAND:
			SV_ParseCommand(client, msg);
			break;
		default:
			Com_DPrintf(S_COLOR_YELLOW "Illegible client message from %s\n", client->name);
			return;
		}

		if (client->state == CS_ZOMBIE)
			return;		// Dropped
	}
}

void SV_OnClientPacket(ENetPeer *peer,ByteBuffer *msg){
	for(int c=0;c<sv_maxClients->integerValue;c++){
		if(svs.clients[c].peer==peer){
			SV_ParseClientMessage(&svs.clients[c],msg);
		}
	}
}
void SV_OnClientDisconnected(ENetPeer *peer){

}
